using System.Reflection;
using System;
using System.Collections.Generic;
using System.Linq;
using ValidationFramework.Configuration.Reflection;

namespace ValidationFramework.Reflection
{
	public static class PropertyInfoExtensions
	{
		// IsHidingMember
		public static bool IsHidingMember(this PropertyInfo self)
		{
			var baseType = self.DeclaringType.BaseType;
			var baseProperty = baseType.GetProperty(self.Name, self.PropertyType);

			if (baseProperty == null)
			{
				return false;
			}

			if (baseProperty.DeclaringType == self.DeclaringType)
			{
				return false;
			}

			var baseMethodDefinition = baseProperty.GetGetMethod().GetBaseDefinition();
			var thisMethodDefinition = self.GetGetMethod().GetBaseDefinition();

			return baseMethodDefinition.DeclaringType != thisMethodDefinition.DeclaringType;
		}

		// Explicit Property
		public static bool IsExplicitPropertyOf(this PropertyInfo self, Type interfaceToCheck)
		{
			if (self == null)
				return false;

			var declaringType = self.DeclaringType;
			// if declaring type itself is an interface assume its not explcit
			if (declaringType.IsInterface)
				return false;

			// ensure we only continue if this type acctually implements the interfaceToCheck
			var implementsInterface = declaringType.GetInterfaces().SingleOrDefault(x => x.Name == interfaceToCheck.Name) != null;
			if (!implementsInterface)
				return false;

			// Get the details of the interface implementation mapping.
			var map = declaringType.GetInterfaceMap(interfaceToCheck);

			// check to see if type explicitly implementes the property
			var propertyGetOrSetMethod = map.TargetMethods.FirstOrDefault(x =>
					x.Name.EndsWith("get_" + self.Name) ||
					x.Name.EndsWith("set_" + self.Name));

			if (propertyGetOrSetMethod == null)
				return false;

			return IsExplicitPropertyMethod(propertyGetOrSetMethod);
		}

		/// <summary>
		/// Checks to see if the supplied methodInfo is explicitly defined
		/// </summary>
		/// <param name="propertyGetOrSetMethod"></param>
		/// <returns>returns true when the supplied methodInfo is explicitly defined</returns>
		private static bool IsExplicitPropertyMethod(MethodInfo propertyGetOrSetMethod)
		{
			return propertyGetOrSetMethod.IsFinal && propertyGetOrSetMethod.IsPrivate;
		}

		public static PropertyKey ToPropertyKey(this PropertyInfo self)
		{
			return new PropertyKey(new TypeKey(self.ReflectedType.TypeHandle), self.Name);
		}

		/// <summary>
		/// Gets attribtues of type TAttribute from the property with the supplied name on the supplied type, including its ancestry. 
		/// </summary>
		/// <typeparam name="TAttribute"></typeparam>
		/// <param name="type"></param>
		/// <param name="propertyName"></param>
		/// <param name="blacklist"></param>
		/// <returns></returns>
		/// <remarks>This skips explicit properties</remarks>
		public static List<RuleAttribute> GetRuleAttributes(this PropertyInfo self)
		{
			return GetAttributes<RuleAttribute>(self, null, true);
		}
		/// <summary>
		/// Gets attribtues of type TAttribute from the property with the supplied name on the supplied type, including its ancestry. 
		/// </summary>
		/// <typeparam name="TAttribute"></typeparam>
		/// <param name="type"></param>
		/// <param name="propertyName"></param>
		/// <param name="blacklist"></param>
		/// <returns></returns>
		/// <remarks>This skips explicit properties</remarks>
		public static List<RuleAttribute> GetRuleAttributes(this PropertyInfo self, List<Type> blacklist, bool includeAncestry)
		{
			return GetAttributes<RuleAttribute>(self, blacklist, includeAncestry);
		}

		private static List<TAttribute> ScanImplicitInterfaces<TAttribute>(PropertyInfo sourceProperty, List<Type> blacklist)
		{
			List<TAttribute> attributeList = new List<TAttribute>();
			var interfaces = sourceProperty.ReflectedType.GetInterfaces();
			foreach (var iface in interfaces)
			{
				if (blacklist.Contains(iface))
					continue;

				// check for property on this interface
				var iProperty = iface.GetProperty(sourceProperty.Name);

				if (iProperty == null)
					continue; // There's no matching property on this interface.

				if (sourceProperty.IsExplicitPropertyOf(iface))
					continue;

				attributeList.AddRange(GetAttributes<TAttribute>(iProperty, blacklist, true));
			}
			return attributeList;
		}
		private static List<TAttribute> ScanBaseClass<TAttribute>(PropertyInfo property, List<Type> blacklist)
		{
			List<TAttribute> attributeList = new List<TAttribute>();
			var baseType = property.ReflectedType.BaseType;
			if (baseType != null && !blacklist.Contains(baseType))
			{
				var baseProperty = baseType.GetProperty(property.Name);

				if (baseProperty != null)
					attributeList.AddRange(GetAttributes<TAttribute>(baseProperty, blacklist, true));
			}
			return attributeList;
		}
		/// <summary>
		/// Gets attribtues of type TAttribute from the property with the supplied name on the supplied type, including its ancestry. 
		/// </summary>
		/// <typeparam name="TAttribute"></typeparam>
		/// <param name="type"></param>
		/// <param name="propertyName"></param>
		/// <param name="blacklist"></param>
		/// <param name="includeAncestry"></param>
		/// <returns></returns>
		/// <remarks>This skips explicit properties</remarks>
		private static List<TAttribute> GetAttributes<TAttribute>(this PropertyInfo self, List<Type> blacklist, bool includeAncestry)
		{
			// Attributes we find will be saved to this list
			List<TAttribute> attributeList = new List<TAttribute>();

			if (blacklist == null)
				blacklist = new List<Type>();

			if (self != null) // walk the hierarchy
			{
				// Scan the current type's implicit interfaces for a matching property and its attributes
				attributeList.AddRange(ScanImplicitInterfaces<TAttribute>(self, blacklist));

				// Scan the current type's base class for a matching property and its attributes
				attributeList.AddRange(ScanBaseClass<TAttribute>(self, blacklist));
			}
			else
				return attributeList;

			// Scan current type's property for attributes
			if (self != null && self.DeclaringType == self.ReflectedType)
			{
				var attributes = self.GetCustomAttributes((typeof(TAttribute)), false);
				foreach (TAttribute attribute in attributes)
				{
					attributeList.Add(attribute);
				}
			}
			// Now that we've scanned the property on this type we don't want to scan this
			// type again. Add the type to the black list
			blacklist.Add(self.ReflectedType);

			return attributeList;

		}

	}
}
